from khronos.utils.misc import Singleton

class Call(object):
    """Utility class to implement callable objects with predefined arguments. Very useful for 
    defining curried (partial) functions."""
    def __init__(self, fnc, *args, **kwargs):
        self.__function = fnc
        self.__args = args
        self.__kwargs = kwargs
        try:
            self.__name__ = fnc.__name__
            self.__doc__ = fnc.__doc__
        except AttributeError:
            self.__name__ = "<no_name>"
            self.__doc__ = "<no_documentation>"
            
    def __str__(self):
        all_args = []
        if len(self.__args) > 0:
            all_args.extend([repr(arg) for arg in self.__args])
        if len(self.__kwargs) > 0:
            all_args.extend(["%s=%r" % (k, v) for k, v in self.__kwargs.iteritems()])
        return "".join([self.__name__, "(", ", ".join(all_args), ")"])
        
    def __call__(self, *args, **kwargs):
        all_args = self.__args
        all_kwargs = self.__kwargs
        if len(args) > 0:
            all_args = self.__args + args
        if len(kwargs) > 0:
            all_kwargs = self.__kwargs.copy()
            all_kwargs.update(kwargs)
        return self.__function(*all_args, **all_kwargs)
        
class DummyCall(Call, Singleton):
    def __init__(self):
        if not hasattr(self, "__name__"):
            Call.__init__(self, DummyCall.dummy)
            DummyCall.__setattr__ = DummyCall.__nosetattr
            
    @staticmethod
    def dummy(*args, **kwargs):
        pass
        
    def __nosetattr(self, attr, value):
        raise TypeError("DummyCall is immutable")
        
    def __call__(self, *args, **kwargs):
        pass
        
class CallGroup(list):
    """This class provides a callable object which in turn may call several functions."""
    @property
    def __name__(self):
        if len(self) > 0:
            return self[0].__name__
        return "<CallGroup[]>"
        
    @property
    def __doc__(self):
        if len(self) > 0:
            return self[0].__doc__
        return ""
        
    def __str__(self):
        return "%s(%s)" % (self.__class__.__name__, 
                           ", ".join([call.__name__ for call in self]))
                           
    def __call__(self, *args, **kwargs):
        if len(self) > 0:
            iterator = iter(self)
            first = iterator.next()
            result = first(*args, **kwargs)
            for call in iterator:
                call(*args, **kwargs)
            return result
            
    def all_results(self, *args, **kwargs):
        for call in self:
            yield call(*args, **kwargs)
            
Call.DUMMY = DummyCall()
Call.Group = CallGroup
